﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Drawing;

namespace System.Windows.Forms;

[TypeConverter(typeof(PaddingConverter))]
[Serializable] // This type is participating in resx serialization scenarios.
[Runtime.CompilerServices.TypeForwardedFrom("System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public struct Padding : IEquatable<Padding>
{
    private bool _all;      // Do NOT rename (binary serialization).
    private int _top;       // Do NOT rename (binary serialization).
    private int _left;      // Do NOT rename (binary serialization).
    private int _right;     // Do NOT rename (binary serialization).
    private int _bottom;    // Do NOT rename (binary serialization).

#pragma warning disable IDE1006 // Naming Styles: Shipped API
    public static readonly Padding Empty = new(0);
#pragma warning restore IDE1006

    public Padding(int all)
    {
        _all = true;
        _top = _left = _right = _bottom = all;
        Debug_SanityCheck();
    }

    public Padding(int left, int top, int right, int bottom)
    {
        _top = top;
        _left = left;
        _right = right;
        _bottom = bottom;
        _all = _top == _left && _top == _right && _top == _bottom;
        Debug_SanityCheck();
    }

    [RefreshProperties(RefreshProperties.All)]
    public int All
    {
        readonly get => _all ? _top : -1;
        set
        {
            if (!_all || _top != value)
            {
                _all = true;
                _top = _left = _right = _bottom = value;
            }

            Debug_SanityCheck();
        }
    }

    [RefreshProperties(RefreshProperties.All)]
    public int Bottom
    {
        readonly get => _all ? _top : _bottom;
        set
        {
            if (_all || _bottom != value)
            {
                _all = false;
                _bottom = value;
            }

            Debug_SanityCheck();
        }
    }

    [RefreshProperties(RefreshProperties.All)]
    public int Left
    {
        readonly get => _all ? _top : _left;
        set
        {
            if (_all || _left != value)
            {
                _all = false;
                _left = value;
            }

            Debug_SanityCheck();
        }
    }

    [RefreshProperties(RefreshProperties.All)]
    public int Right
    {
        readonly get => _all ? _top : _right;
        set
        {
            if (_all || _right != value)
            {
                _all = false;
                _right = value;
            }

            Debug_SanityCheck();
        }
    }

    [RefreshProperties(RefreshProperties.All)]
    public int Top
    {
        readonly get => _top;
        set
        {
            if (_all || _top != value)
            {
                _all = false;
                _top = value;
            }

            Debug_SanityCheck();
        }
    }

    [Browsable(false)]
    public readonly int Horizontal => Left + Right;

    [Browsable(false)]
    public readonly int Vertical => Top + Bottom;

    [Browsable(false)]
    public readonly Size Size => new(Horizontal, Vertical);

    public static Padding Add(Padding p1, Padding p2) => p1 + p2;

    public static Padding Subtract(Padding p1, Padding p2) => p1 - p2;

#pragma warning disable CA1725 // Parameter names should match base declaration. Shipped API.
    public override readonly bool Equals(object? other) => other is Padding otherPadding && Equals(otherPadding);
#pragma warning restore CA1725

    public readonly bool Equals(Padding other) =>
        Left == other.Left
            && Top == other.Top
            && Right == other.Right
            && Bottom == other.Bottom;

    /// <summary>
    ///  Performs vector addition of two <see cref="Padding"/> objects.
    /// </summary>
    public static Padding operator +(Padding p1, Padding p2) =>
        new(p1.Left + p2.Left, p1.Top + p2.Top, p1.Right + p2.Right, p1.Bottom + p2.Bottom);

    /// <summary>
    ///  Contracts a <see cref="Drawing.Size"/> by another <see cref="Drawing.Size"/>.
    /// </summary>
    public static Padding operator -(Padding p1, Padding p2) =>
        new(p1.Left - p2.Left, p1.Top - p2.Top, p1.Right - p2.Right, p1.Bottom - p2.Bottom);

    /// <summary>
    ///  Tests whether two <see cref="Padding"/> objects are identical.
    /// </summary>
    public static bool operator ==(Padding p1, Padding p2) =>
        p1.Left == p2.Left && p1.Top == p2.Top && p1.Right == p2.Right && p1.Bottom == p2.Bottom;

    /// <summary>
    ///  Tests whether two <see cref="Padding"/> objects are different.
    /// </summary>
    public static bool operator !=(Padding p1, Padding p2) => !(p1 == p2);

    public override readonly int GetHashCode() => HashCode.Combine(Left, Top, Right, Bottom);

    public override readonly string ToString() => $"{{Left={Left},Top={Top},Right={Right},Bottom={Bottom}}}";

    private void ResetAll() => All = 0;

    private void ResetBottom() => Bottom = 0;

    private void ResetLeft() => Left = 0;

    private void ResetRight() => Right = 0;

    private void ResetTop() => Top = 0;

    internal void Scale(float dx, float dy)
    {
        _top = (int)(_top * dy);
        _left = (int)(_left * dx);
        _right = (int)(_right * dx);
        _bottom = (int)(_bottom * dy);
    }

    internal readonly bool ShouldSerializeAll() => _all;

    [Conditional("DEBUG")]
    private readonly void Debug_SanityCheck()
    {
        if (_all)
        {
            Debug.Assert(ShouldSerializeAll(), "_all is true, but ShouldSerializeAll() is false.");
            Debug.Assert(All == Left && Left == Top && Top == Right && Right == Bottom, "_all is true, but All/Left/Top/Right/Bottom inconsistent.");
        }
        else
        {
            Debug.Assert(All == -1, "_all is false, but All != -1.");
            Debug.Assert(!ShouldSerializeAll(), "ShouldSerializeAll() should not be true when all flag is not set.");
        }
    }
}
